package game;

import io.IOManager;
import player.Player;
import validation.Evaluator;
import card.Card;
import card.Cardset;
import card.Deck;

/**
 * Game class controls the game's flow. Most game flow controlling logic could
 * be found here.
 * 
 * 
 * @version 2.2.0
 * 
 */
public class Game {
	// Attributes

	private Player lastWinner;
	private Player landLord;
	private Player[] players;

	private Evaluator evaluator;
	private Cardset deck;
	private IOManager ioManager;

	/**
	 * Initializes a Game object
	 */
	public Game(Player[] players, IOManager ioManager) {
		this(players, ioManager, null);
	}

	/**
	 * Initializes a Game object
	 */
	public Game(Player[] players, IOManager ioManager, Player lastWinner) {
		this(players, new Evaluator(), new Deck(), ioManager, lastWinner);
	}

	/**
	 * Initializes a Game object
	 */
	public Game(Player[] players, Evaluator evaluator, Cardset deck,
			IOManager ioManager, Player lastWinner) {
		this.players = players;
		this.evaluator = evaluator;
		this.deck = deck;
		this.lastWinner = lastWinner;
		this.ioManager = ioManager;
	}

	/**
	 * Start a new Battling the Landlord game
	 */
	public Player start() {
		// deck.refresh;
		((Deck)deck).shuffle();

		for (int i = 0; i < 3; i++) {
			players[i].clearHand();
		}

		dealCardsForPlayer();

		LandlordElection election = new LandlordElection(players, lastWinner);
		landLord = election.elect();

		dealCardsForLandlord(landLord);

		gameLoop();

		ioManager.ioEndGameMessage();

		return getWinner();
	}

	/**
	 * Check if a play is valid
	 * 
	 * @param play
	 * @return
	 */
	public boolean acceptPlay(Cardset play) {
		return evaluator.successHand(new Cardset(play));
	}

	/**
	 * return the winner
	 * 
	 * @return
	 */
	public Player getWinner() {
		for (int i = 0; i < 3; i++) {
			if (players[i].countHand() == 0) {
				return players[i];
			}
		}
		return null;
	}

	/**
	 * Deal 3 further-added cards to landlord
	 * 
	 * @param landLord
	 */
	private void dealCardsForLandlord(Player landLord) {
		if (landLord != null) {
			for (int i = 0; i < 3; i++) {
				Card card = drawOneCardFromDeck();
				landLord.deckDeal2Hand(card);
			}
			landLord.sortHand();
		}
	}

	/**
	 * Distribute all card to 3 players
	 */
	private void dealCardsForPlayer() {
		int lastWinnerID = 0;
		int numberOfCardsToDeal = 17;

		if (lastWinner != null)
			lastWinnerID = lastWinner.getID();

		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < numberOfCardsToDeal; j++) {
				int k = (i + lastWinnerID) % 3;
				Card card = drawOneCardFromDeck();
				players[k].deckDeal2Hand(card);
			}

			players[i].sortHand();
		}
	}

	/**
	 * check if the game is ended
	 * 
	 * @return
	 */
	private boolean endOfGame() {
		return (getWinner() != null);
	}

	/**
	 * The main process of each tround of the Game
	 * 
	 * @param gameTurn
	 */
	private void processTurn(GameTurn gameTurn) {
		TurnResult result;
		do {
			result = gameTurn.play(ioManager, this);
		} while (result == TurnResult.PENDING);
	}

	/**
	 * Loop the turn if the game is not determined to be end
	 */
	private void gameLoop() {
		Player newRoundStarter = players[0];

		if (landLord != null)
			newRoundStarter = landLord;
		else if (lastWinner != null)
			newRoundStarter = lastWinner;

		boolean bFirstRound = true;

		do {
			GameRound gameRound = new GameRound(players, newRoundStarter,
					evaluator);

			if (newRoundStarter != null && !bFirstRound)
				ioManager.ioNewRoundMessage(newRoundStarter.getName());

			do {
				GameTurn currentTurn;
				currentTurn = gameRound.NextTurn();
				processTurn(currentTurn);
			} while (!gameRound.isEndOfRound() && !endOfGame());

			if (bFirstRound)
				bFirstRound = false;
			newRoundStarter = gameRound.getNextPlayer();
		} while (!endOfGame());
	}

	/**
	 * Draw a from top of the deck
	 * 
	 * @return A card object represents the card
	 */
	public Card drawOneCardFromDeck() {
		return deck.takeOut(deck.count() - 1);
	}
}
